/* 数据结构
Task: {
  id, title, startAt, dueAt, remindAt, completedAt, progress, memo, quadrant, collapsed, deleted
}
Idea: { id, content, time }
Trash: { id, kind: 'task'|'idea', payload, deletedAt }
Config: { theme, reminderThresholdMinutes, minimapLocked, storage: 'local'|'file'|'remote', dataFile, configFile, remoteLoad, remoteSave }
*/

const $ = (sel, el = document) => el.querySelector(sel);
const $$ = (sel, el = document) => Array.from(el.querySelectorAll(sel));

const defaultConfig = {
  theme: 'light',
  reminderThresholdMinutes: 30,
  minimapLocked: true,
  storage: 'local',
  dataFile: 'tasks.json',
  configFile: 'config.json',
  remoteLoad: '',
  remoteSave: ''
};

let state = {
  tasks: [],
  ideas: [],
  trash: [],
  config: { ...defaultConfig }
};

const KEYS = {
  tasks: 'matrix.tasks',
  ideas: 'matrix.ideas',
  trash: 'matrix.trash',
  config: 'matrix.config'
};

/* Storage Providers */
const Storage = {
  provider: null,
  setProvider(name) {
    if (name === 'file') this.provider = FileStorage;
    else if (name === 'remote') this.provider = RemoteStorage;
    else this.provider = LocalStorage; // default
  },
  async loadAll() { return this.provider.loadAll(); },
  async saveAll(s) { return this.provider.saveAll(s); },
  async loadConfig() { return this.provider.loadConfig(); },
  async saveConfig(cfg) { return this.provider.saveConfig(cfg); }
};

/* LocalStorage Provider */
const LocalStorage = {
  async loadAll() {
    return {
      tasks: JSON.parse(localStorage.getItem(KEYS.tasks) || '[]'),
      ideas: JSON.parse(localStorage.getItem(KEYS.ideas) || '[]'),
      trash: JSON.parse(localStorage.getItem(KEYS.trash) || '[]'),
      config: JSON.parse(localStorage.getItem(KEYS.config) || 'null') || { ...defaultConfig }
    };
  },
  async saveAll(s) {
    localStorage.setItem(KEYS.tasks, JSON.stringify(s.tasks));
    localStorage.setItem(KEYS.ideas, JSON.stringify(s.ideas));
    localStorage.setItem(KEYS.trash, JSON.stringify(s.trash));
    localStorage.setItem(KEYS.config, JSON.stringify(s.config));
    return true;
  },
  async loadConfig() {
    return JSON.parse(localStorage.getItem(KEYS.config) || 'null') || { ...defaultConfig };
  },
  async saveConfig(cfg) {
    localStorage.setItem(KEYS.config, JSON.stringify(cfg));
    return true;
  }
};

/* File Storage Provider（同目录JSON；浏览器中需手动导入/导出作为替代） */
const FileStorage = {
  async loadAll() {
    // 浏览器环境中无法直接读取同目录文件，fallback到LocalStorage
    const ls = await LocalStorage.loadAll();
    return { ...ls };
  },
  async saveAll(s) {
    // 浏览器直接保存同目录不可行；使用导出按钮导出为JSON文件。
    await LocalStorage.saveAll(s);
    return true;
  },
  async loadConfig() {
    const cfg = await LocalStorage.loadConfig();
    return cfg;
  },
  async saveConfig(cfg) {
    await LocalStorage.saveConfig(cfg);
    return true;
  }
};

/* Remote Provider（需要后端实现对应接口） */
const RemoteStorage = {
  async loadAll() {
    const cfg = await this.loadConfig();
    if (!cfg.remoteLoad) return LocalStorage.loadAll();
    try {
      const res = await fetch(cfg.remoteLoad, { credentials: 'include' });
      if (!res.ok) throw new Error('load failed');
      const data = await res.json();
      return {
        tasks: data.tasks || [],
        ideas: data.ideas || [],
        trash: data.trash || [],
        config: data.config || cfg
      };
    } catch (e) {
      console.warn('远端加载失败，回退本地', e);
      return LocalStorage.loadAll();
    }
  },
  async saveAll(s) {
    const cfg = s.config || (await this.loadConfig());
    if (!cfg.remoteSave) return LocalStorage.saveAll(s);
    try {
      const res = await fetch(cfg.remoteSave, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'include',
        body: JSON.stringify({ tasks: s.tasks, ideas: s.ideas, trash: s.trash, config: s.config })
      });
      if (!res.ok) throw new Error('save failed');
      return true;
    } catch (e) {
      console.warn('远端保存失败，保存到本地', e);
      return LocalStorage.saveAll(s);
    }
  },
  async loadConfig() {
    const cfg = await LocalStorage.loadConfig();
    return cfg || { ...defaultConfig };
  },
  async saveConfig(cfg) {
    return LocalStorage.saveConfig(cfg);
  }
};

/* 初始化 */
document.addEventListener('DOMContentLoaded', init);

async function init() {
  // 先载入配置，决定Provider
  const cfg0 = await LocalStorage.loadConfig();
  state.config = { ...defaultConfig, ...cfg0 };
  Storage.setProvider(state.config.storage);

  const all = await Storage.loadAll();
  state.tasks = all.tasks || [];
  state.ideas = all.ideas || [];
  state.trash = all.trash || [];
  state.config = { ...defaultConfig, ...all.config };

  // 主题
  document.documentElement.setAttribute('data-theme', state.config.theme);
  $('#themeSwitcher').value = state.config.theme;

  // UI绑定
  bindUI();

  // 初始渲染
  renderAll();

  // 定时提醒检测
  setInterval(checkDueSoon, 30 * 1000); // 30秒检查一次
  checkDueSoon();
}

function bindUI() {
  $('#btnNewTask').addEventListener('click', () => openTaskDialog());
  $('#btnNewIdea').addEventListener('click', () => openIdeaDialog());
  $('#btnSettings').addEventListener('click', () => openSettingsDialog());

  $('#themeSwitcher').addEventListener('change', (e) => {
    const theme = e.target.value;
    document.documentElement.setAttribute('data-theme', theme);
    state.config.theme = theme;
    persist();
  });

  $('#btnToggleMinimapLock').addEventListener('click', () => {
    const mini = $('#minimap');
    const locked = mini.getAttribute('data-locked') === 'true';
    mini.setAttribute('data-locked', String(!locked));
    state.config.minimapLocked = !locked;
    persist();
  });

  // 导出/导入
  $('#btnExport').addEventListener('click', exportData);
  $('#btnImport').addEventListener('click', importData);

  // 想法区
  $('#btnClearIdeas').addEventListener('click', () => {
    if (confirm('清空临时想法？')) {
      state.ideas = [];
      persist();
      renderIdeas();
    }
  });

  // 回收站
  $('#btnEmptyTrash').addEventListener('click', () => {
    if (confirm('清空回收站将彻底删除所有条目，确定？')) {
      state.trash = [];
      persist();
      renderTrash();
    }
  });

  // 弹窗提交
  $('#taskForm').addEventListener('submit', onTaskFormSubmit);
  $('#ideaForm').addEventListener('submit', onIdeaFormSubmit);
  $('#settingsForm').addEventListener('submit', onSettingsFormSubmit);

  // 存储方式切换显示额外项
  $('#cfgStorage').addEventListener('change', onStorageModeChange);

  // 小地图拖拽
  initMinimapDrag();

  // 四象限拖拽接收
  $$('.droppable').forEach(zone => {
    zone.addEventListener('dragover', onDragOver);
    zone.addEventListener('dragleave', onDragLeave);
    zone.addEventListener('drop', onDropToQuadrant);
  });

  // 想法区允许接收“退化”为想法
  $('#ideasList').addEventListener('dragover', onDragOver);
  $('#ideasList').addEventListener('dragleave', onDragLeave);
  $('#ideasList').addEventListener('drop', onDropToIdeas);
}

/* 渲染 */
function renderAll() {
  renderQuadrants();
  renderIdeas();
  renderTrash();
  renderMinimap();
}

function renderQuadrants() {
  ['q1','q2','q3','q4'].forEach(q => {
    const box = $('#' + q);
    box.innerHTML = '';
    state.tasks.filter(t => !t.deleted && t.quadrant === q).forEach(t => {
      box.appendChild(renderTaskCard(t));
    });
  });
}

// function renderTaskCard(task) {
//   const tpl = $('#taskCardTpl').content.cloneNode(true);
//   const card = tpl.querySelector('.task-card');
//   card.dataset.id = task.id;

//   const dragHandle = card.querySelector('.drag-handle');
//   dragHandle.addEventListener('dragstart', (e) => onDragStartTask(e, task));
//   // 限制拖拽源：只有手柄允许开始拖拽
//   card.addEventListener('dragstart', (e) => {
//     if (!e.target.classList.contains('drag-handle') && !e.target.closest('.drag-handle')) {
//       e.preventDefault();
//     }
//   });

//   card.querySelector('.title').textContent = task.title;
//   card.querySelector('.startAt').textContent = fmtDate(task.startAt);
//   card.querySelector('.dueAt').textContent = fmtDate(task.dueAt);
//   card.querySelector('.remindAt').textContent = fmtDate(task.remindAt);
//   const memo = card.querySelector('.memo-content');
//   memo.textContent = task.memo || '';

//   const range = card.querySelector('.progress-range');
//   const progressText = card.querySelector('.progress-text');
//   range.value = task.progress || 0;
//   progressText.textContent = `${task.progress || 0}%`;
//   range.addEventListener('input', (e) => {
//     e.stopPropagation(); // 阻止触发拖卡片
//     const val = clamp(parseInt(e.target.value || '0', 10), 0, 100);
//     progressText.textContent = `${val}%`;
//   });
//   range.addEventListener('change', (e) => {
//     const val = clamp(parseInt(e.target.value || '0', 10), 0, 100);
//     task.progress = val;
//     if (val === 100 && !task.completedAt) task.completedAt = new Date().toISOString();
//     persist();
//     checkDueSoon();
//     renderMinimap();
//   });

//   // Actions
//   card.querySelector('[data-action="toggle"]').addEventListener('click', () => {
//     task.collapsed = !task.collapsed;
//     card.querySelector('.task-body').style.display = task.collapsed ? 'none' : '';
//     persist();
//   });
//   card.querySelector('[data-action="edit"]').addEventListener('click', () => openTaskDialog(task));
//   card.querySelector('[data-action="done"]').addEventListener('click', () => {
//     task.progress = 100;
//     task.completedAt = new Date().toISOString();
//     persist();
//     renderAll();
//   });
//   card.querySelector('[data-action="delete"]').addEventListener('click', () => {
//     const del = { id: genId(), kind: 'task', payload: task, deletedAt: new Date().toISOString() };
//     state.trash.unshift(del);
//     task.deleted = true;
//     persist();
//     renderAll();
//   });

//   // 折叠状态
//   if (task.collapsed) card.querySelector('.task-body').style.display = 'none';

//   // 到期变色
//   if (isDueSoon(task)) card.classList.add('due-soon');

//   return card;
// }

function renderTaskCard(task) {
  const tpl = $('#taskCardTpl').content.cloneNode(true);
  const card = tpl.querySelector('.task-card');
  card.dataset.id = task.id;

  // 保证整个卡片默认可拖拽
  card.setAttribute('draggable', 'true');

  // 设置头部内容
  card.querySelector('.title').textContent = task.title;
  card.querySelector('.startAt').textContent = fmtDate(task.startAt);
  card.querySelector('.dueAt').textContent = fmtDate(task.dueAt);
  card.querySelector('.remindAt').textContent = fmtDate(task.remindAt);
  card.querySelector('.memo-content').textContent = task.memo || '';

  // 进度条逻辑
  const range = card.querySelector('.progress-range');
  const progressText = card.querySelector('.progress-text');
  range.value = task.progress || 0;
  progressText.textContent = `${task.progress || 0}%`;
  range.addEventListener('input', e => {
    e.stopPropagation();
    const val = clamp(parseInt(e.target.value || '0', 10), 0, 100);
    progressText.textContent = `${val}%`;
  });
  range.addEventListener('change', e => {
    const val = clamp(parseInt(e.target.value || '0', 10), 0, 100);
    task.progress = val;
    if (val === 100 && !task.completedAt) task.completedAt = new Date().toISOString();
    persist();
    checkDueSoon();
    renderMinimap();
  });

  // ==== 拖拽绑定 ====
  card.addEventListener('dragstart', e => {
    // 只允许从拖拽手柄开始
    if (!e.target.closest('.drag-handle')) {
      e.preventDefault();
      return;
    }
    e.dataTransfer.effectAllowed = 'move';
    e.dataTransfer.setData('text/plain', JSON.stringify({ kind: 'task', id: task.id }));
    card.classList.add('dragging');
  });
  card.addEventListener('dragend', () => {
    card.classList.remove('dragging');
  });

  // 卡片按钮动作
  card.querySelector('[data-action="toggle"]').addEventListener('click', () => {
    task.collapsed = !task.collapsed;
    card.querySelector('.task-body').style.display = task.collapsed ? 'none' : '';
    persist();
  });
  card.querySelector('[data-action="edit"]').addEventListener('click', () => openTaskDialog(task));
  card.querySelector('[data-action="done"]').addEventListener('click', () => {
    task.progress = 100;
    task.completedAt = new Date().toISOString();
    persist();
    renderAll();
  });
  card.querySelector('[data-action="delete"]').addEventListener('click', () => {
    const del = { id: genId(), kind: 'task', payload: task, deletedAt: new Date().toISOString() };
    state.trash.unshift(del);
    task.deleted = true;
    persist();
    renderAll();
  });

  if (task.collapsed) card.querySelector('.task-body').style.display = 'none';
  if (isDueSoon(task)) card.classList.add('due-soon');

  return card;
}



function renderIdeas() {
  const box = $('#ideasList');
  box.innerHTML = '';
  state.ideas.forEach(idea => {
    const node = renderIdeaCard(idea);
    box.appendChild(node);
  });
}

function renderIdeaCard(idea) {
  const tpl = $('#ideaCardTpl').content.cloneNode(true);
  const card = tpl.querySelector('.idea-card');
  card.dataset.id = idea.id;

  const dragHandle = card.querySelector('.drag-handle');
  dragHandle.addEventListener('dragstart', (e) => onDragStartIdea(e, idea));
  card.addEventListener('dragstart', (e) => {
    if (!e.target.classList.contains('drag-handle') && !e.target.closest('.drag-handle')) {
      e.preventDefault();
    }
  });

  card.querySelector('.content').textContent = idea.content;
  card.querySelector('.time').textContent = idea.time ? fmtDate(idea.time) : '—';
  card.querySelector('[data-action="promote"]').addEventListener('click', () => {
    promoteIdeaToTask(idea, 'q2'); // 默认转到“不紧急&重要”
  });
  card.querySelector('[data-action="delete"]').addEventListener('click', () => {
    const del = { id: genId(), kind: 'idea', payload: idea, deletedAt: new Date().toISOString() };
    state.trash.unshift(del);
    state.ideas = state.ideas.filter(i => i.id !== idea.id);
    persist();
    renderIdeas();
    renderTrash();
  });
  return card;
}

function renderTrash() {
  const box = $('#trashList');
  box.innerHTML = '';
  const tpl = $('#trashItemTpl');
  state.trash.forEach(item => {
    const node = tpl.content.cloneNode(true);
    const el = node.querySelector('.trash-item');
    el.dataset.id = item.id;
    el.querySelector('.label').textContent = item.kind === 'task' ? `任务：${item.payload.title}` : `想法：${truncate(item.payload.content, 24)}`;
    el.querySelector('.info').textContent = `删除于 ${fmtDate(item.deletedAt)}`;

    el.querySelector('[data-action="restore"]').addEventListener('click', () => {
      if (item.kind === 'task') {
        const t = { ...item.payload, deleted: false };
        upsertTask(t);
      } else {
        state.ideas.unshift(item.payload);
      }
      state.trash = state.trash.filter(x => x.id !== item.id);
      persist();
      renderAll();
    });
    el.querySelector('[data-action="purge"]').addEventListener('click', () => {
      state.trash = state.trash.filter(x => x.id !== item.id);
      persist();
      renderTrash();
    });

    box.appendChild(node);
  });
}

function renderMinimap() {
  // 简单显示每象限数量和平均进度
  const byQ = { q1: [], q2: [], q3: [], q4: [] };
  state.tasks.filter(t => !t.deleted).forEach(t => byQ[t.quadrant]?.push(t));
  Object.entries(byQ).forEach(([q, arr]) => {
    const box = $('#mini-' + q);
    const count = arr.length;
    const avg = count ? Math.round(arr.reduce((a,b)=>a+(b.progress||0),0)/count) : 0;
    box.innerHTML = `<div><span class="count">${count}</span> 项</div><div>均进度 ${avg}%</div>`;
  });
}

/* 拖拽 */
function onDragStartTask(e, task) {
  e.dataTransfer.setData('text/plain', JSON.stringify({ kind: 'task', id: task.id }));
  e.dataTransfer.effectAllowed = 'move';
}

function onDragStartIdea(e, idea) {
  e.dataTransfer.setData('text/plain', JSON.stringify({ kind: 'idea', id: idea.id }));
  e.dataTransfer.effectAllowed = 'copyMove';
}

function onDragOver(e) {
  e.preventDefault();
  e.dataTransfer.dropEffect = 'move';
  e.currentTarget.classList.add('drag-over');
}
function onDragLeave(e) {
  e.currentTarget.classList.remove('drag-over');
}

function onDropToQuadrant(e) {
  e.preventDefault();
  e.currentTarget.classList.remove('drag-over');
  let data = null;
  try { data = JSON.parse(e.dataTransfer.getData('text/plain')); } catch {}
  if (!data) return;

  const q = e.currentTarget.dataset.quadrant;
  if (data.kind === 'task') {
    const t = state.tasks.find(x => x.id === data.id);
    if (t) {
      t.quadrant = q;
      persist();
      renderAll();
    }
  } else if (data.kind === 'idea') {
    const idea = state.ideas.find(x => x.id === data.id);
    if (idea) promoteIdeaToTask(idea, q);
  }
}

function onDropToIdeas(e) {
  e.preventDefault();
  e.currentTarget.classList.remove('drag-over');
  let data = null;
  try { data = JSON.parse(e.dataTransfer.getData('text/plain')); } catch {}
  if (!data) return;
  if (data.kind === 'task') {
    const t = state.tasks.find(x => x.id === data.id);
    if (t) {
      // 任务退化为想法
      const idea = { id: genId(), content: t.title + (t.memo ? ('｜' + t.memo) : ''), time: new Date().toISOString() };
      state.ideas.unshift(idea);
      // 丢入回收站保留原任务（可还原）
      const del = { id: genId(), kind: 'task', payload: t, deletedAt: new Date().toISOString() };
      state.trash.unshift(del);
      t.deleted = true;
      persist();
      renderAll();
    }
  }
}

/* 弹窗 */
function openTaskDialog(task) {
  const dlg = $('#taskDialog');
  $('#taskDialogTitle').textContent = task ? '编辑任务' : '新建任务';
  $('#taskId').value = task?.id || '';
  $('#taskTitle').value = task?.title || '';
  $('#taskQuadrant').value = task?.quadrant || 'q2';
  $('#taskStartAt').value = toInputValue(task?.startAt);
  $('#taskDueAt').value = toInputValue(task?.dueAt);
  $('#taskRemindAt').value = toInputValue(task?.remindAt);
  $('#taskProgress').value = task?.progress ?? 0;
  $('#taskMemo').value = task?.memo || '';
  dlg.showModal();
}

function onTaskFormSubmit(e) {
  e.preventDefault();
  const id = $('#taskId').value || genId();
  const task = {
    id,
    title: $('#taskTitle').value.trim(),
    quadrant: $('#taskQuadrant').value,
    startAt: fromInputValue($('#taskStartAt').value),
    dueAt: fromInputValue($('#taskDueAt').value),
    remindAt: fromInputValue($('#taskRemindAt').value),
    progress: clamp(parseInt($('#taskProgress').value || '0', 10), 0, 100),
    memo: $('#taskMemo').value,
    collapsed: false,
    deleted: false
  };
  upsertTask(task);
  $('#taskDialog').close();
  persist();
  renderAll();
}

function openIdeaDialog() {
  $('#ideaContent').value = '';
  $('#ideaTime').value = toInputValue(new Date().toISOString());
  $('#ideaDialog').showModal();
}

function onIdeaFormSubmit(e) {
  e.preventDefault();
  const idea = {
    id: genId(),
    content: $('#ideaContent').value.trim(),
    time: fromInputValue($('#ideaTime').value)
  };
  state.ideas.unshift(idea);
  $('#ideaDialog').close();
  persist();
  renderIdeas();
}

function openSettingsDialog() {
  $('#cfgTheme').value = state.config.theme;
  $('#cfgReminderThreshold').value = state.config.reminderThresholdMinutes;
  $('#cfgMinimapLocked').value = String(state.config.minimapLocked);
  $('#cfgStorage').value = state.config.storage;
  $('#cfgDataFile').value = state.config.dataFile;
  $('#cfgConfigFile').value = state.config.configFile;
  $('#cfgRemoteLoad').value = state.config.remoteLoad;
  $('#cfgRemoteSave').value = state.config.remoteSave;

  applyStorageVisibility(state.config.storage);
  $('#settingsDialog').showModal();
}

function onSettingsFormSubmit(e) {
  e.preventDefault();
  const cfg = {
    ...state.config,
    theme: $('#cfgTheme').value,
    reminderThresholdMinutes: parseInt($('#cfgReminderThreshold').value || '30', 10),
    minimapLocked: $('#cfgMinimapLocked').value === 'true',
    storage: $('#cfgStorage').value,
    dataFile: $('#cfgDataFile').value || 'tasks.json',
    configFile: $('#cfgConfigFile').value || 'config.json',
    remoteLoad: $('#cfgRemoteLoad').value.trim(),
    remoteSave: $('#cfgRemoteSave').value.trim()
  };
  state.config = cfg;
  document.documentElement.setAttribute('data-theme', cfg.theme);
  $('#minimap').setAttribute('data-locked', String(cfg.minimapLocked));

  Storage.setProvider(cfg.storage);
  persist(); // 保存到当前Provider（如远端失败则回退本地）

  $('#settingsDialog').close();
}

function onStorageModeChange(e) {
  applyStorageVisibility(e.target.value);
}
function applyStorageVisibility(mode) {
  $$('[data-storage]').forEach(el => {
    el.style.display = el.getAttribute('data-storage') === mode ? '' : 'none';
  });
}

/* 逻辑 */
function upsertTask(task) {
  const idx = state.tasks.findIndex(t => t.id === task.id);
  if (idx >= 0) state.tasks[idx] = { ...state.tasks[idx], ...task };
  else state.tasks.unshift(task);
}

function promoteIdeaToTask(idea, quadrant) {
  const task = {
    id: genId(),
    title: idea.content.split('\n')[0].slice(0, 50),
    quadrant: quadrant || 'q2',
    startAt: new Date().toISOString(),
    dueAt: null,
    remindAt: null,
    progress: 0,
    memo: idea.content,
    collapsed: false,
    deleted: false
  };
  upsertTask(task);
  state.ideas = state.ideas.filter(i => i.id !== idea.id);
  persist();
  renderAll();
}

function isDueSoon(task) {
  const now = Date.now();
  const thresholdMs = (state.config.reminderThresholdMinutes || 30) * 60 * 1000;
  const remindAt = toMs(task.remindAt);
  const dueAt = toMs(task.dueAt);

  const hitRemind = remindAt && now >= remindAt;
  const nearDue = dueAt && now >= (dueAt - thresholdMs) && (task.progress || 0) < 100;
  return !!(hitRemind || nearDue);
}

function checkDueSoon() {
  $$('.task-card').forEach(card => {
    const id = card.dataset.id;
    const t = state.tasks.find(x => x.id === id);
    if (!t) return;
    if (isDueSoon(t)) card.classList.add('due-soon'); else card.classList.remove('due-soon');
  });
}

/* 小地图拖拽与锁定 */
function initMinimapDrag() {
  const mini = $('#minimap');
  let dragging = false;
  let sx = 0, sy = 0, startLeft = 0, startTop = 0;
  mini.addEventListener('mousedown', (e) => {
    if (mini.getAttribute('data-locked') === 'true') return;
    if (!e.target.closest('.minimap-header')) return;
    dragging = true;
    sx = e.clientX; sy = e.clientY;
    const rect = mini.getBoundingClientRect();
    startLeft = rect.left; startTop = rect.top;
    document.addEventListener('mousemove', onMove);
    document.addEventListener('mouseup', onUp);
    e.preventDefault();
  });
  function onMove(e) {
    if (!dragging) return;
    const dx = e.clientX - sx;
    const dy = e.clientY - sy;
    mini.style.left = (startLeft + dx) + 'px';
    mini.style.top = (startTop + dy) + 'px';
    mini.style.right = 'auto';
    mini.style.bottom = 'auto';
  }
  function onUp() {
    dragging = false;
    document.removeEventListener('mousemove', onMove);
    document.removeEventListener('mouseup', onUp);
  }
}

/* 导入/导出（文件方式） */
async function exportData() {
  const blob = new Blob([JSON.stringify({
    tasks: state.tasks, ideas: state.ideas, trash: state.trash, config: state.config
  }, null, 2)], { type: 'application/json' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url; a.download = state.config.dataFile || 'tasks.json';
  document.body.appendChild(a); a.click(); a.remove();
  URL.revokeObjectURL(url);
}

async function importData() {
  const inp = document.createElement('input');
  inp.type = 'file'; inp.accept = 'application/json';
  inp.onchange = async () => {
    const file = inp.files[0];
    if (!file) return;
    const text = await file.text();
    try {
      const data = JSON.parse(text);
      state.tasks = Array.isArray(data.tasks) ? data.tasks : state.tasks;
      state.ideas = Array.isArray(data.ideas) ? data.ideas : state.ideas;
      state.trash = Array.isArray(data.trash) ? data.trash : state.trash;
      state.config = { ...state.config, ...(data.config || {}) };
      document.documentElement.setAttribute('data-theme', state.config.theme);
      $('#minimap').setAttribute('data-locked', String(state.config.minimapLocked));
      persist();
      renderAll();
      alert('导入成功');
    } catch (e) {
      alert('导入失败：' + e.message);
    }
  };
  inp.click();
}

/* 工具 */
function persist() {
  Storage.saveAll(state).catch(err => console.warn('保存失败', err));
}
function fmtDate(iso) {
  if (!iso) return '—';
  try {
    const d = new Date(iso);
    if (Number.isNaN(d.getTime())) return '—';
    const pad = (n)=> String(n).padStart(2,'0');
    const yyyy = d.getFullYear(), MM = pad(d.getMonth()+1), dd = pad(d.getDate());
    const hh = pad(d.getHours()), mm = pad(d.getMinutes());
    return `${yyyy}-${MM}-${dd} ${hh}:${mm}`;
  } catch { return '—'; }
}
function toInputValue(iso) {
  if (!iso) return '';
  const d = new Date(iso);
  if (Number.isNaN(d.getTime())) return '';
  const pad = (n)=> String(n).padStart(2,'0');
  return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
}
function fromInputValue(val) {
  return val ? new Date(val).toISOString() : null;
}
function toMs(iso) {
  if (!iso) return null;
  const t = new Date(iso).getTime();
  return Number.isNaN(t) ? null : t;
}
function clamp(n, min, max) { return Math.max(min, Math.min(max, n)); }
function genId() { return 'id_' + Math.random().toString(36).slice(2, 10) + Date.now().toString(36); }
function truncate(s, n) { return s.length > n ? s.slice(0, n - 1) + '…' : s; }
